package net.fltx.p2p.client;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import javafx.stage.Stage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import net.fltx.p2p.common.*;

import javax.annotation.PreDestroy;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author fltx.net
 * @Date 2021/2/9 12:34
 * @Version 1.0
 */
@Component
@Slf4j
public class SelfP2PClient extends P2PClientInfo {
    @Value("${server_address}")
    private String SERVER_ADDRESS;
    @Value("${server_port}")
    private int SERVER_PORT;

    @Autowired
    MainForm mainForm;

    @Autowired
    P2PClientApp p2PClientApp;

    @Autowired
    MainFormController mainFormController;
    /**
     * 绑定本地地址
     */
    private SocketAddress bindLocalAddr;
    /** p2p服务端 */
    private SocketChannel p2pServer;
    /** p2p监听 */
    private ServerSocketChannel monitorSocket;
    /** 线程池 */
    private ExecutorService executorService;
    private Selector selector;

    @Value("${username}")
    @Override
    public void setUsername(String username) {
        this.username = username;
    }

    @Value("${uuid}")
    @Override
    public void setUuid(String uuid){
        log.info("uuid: "+uuid);
        if(uuid==null || uuid.equals("")){
            this.uuid = UUID.randomUUID().toString();
            try {
                String ym="username: "+(this.username==null? this.uuid:this.username)+"\n"
                        + "uuid: "+this.uuid;
                String path = System.getProperty("user.dir");
                log.info("path: "+path);
                FileOutputStream fos=new FileOutputStream(path+"/application-user.yml");
                fos.write(ym.getBytes(StandardCharsets.UTF_8));
                fos.flush();
                fos.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }else {
            this.uuid=uuid;
        }
    }


    public SelfP2PClient() {
        super();
        executorService = Executors.newFixedThreadPool(16);
        log.info("启动SelfP2PClient");
    }

    public void connectP2PServer() throws IOException {

        p2pServer = SocketChannel.open();
        p2pServer.socket().setReuseAddress(true);
        if(bindLocalAddr !=null) p2pServer.bind(bindLocalAddr);
        p2pServer.connect(new InetSocketAddress(SERVER_ADDRESS,SERVER_PORT));
        if(bindLocalAddr==null) bindLocalAddr= p2pServer.getLocalAddress();

        p2pServer.configureBlocking(false);
        p2pServer.register(selector, SelectionKey.OP_READ );
        executorService.execute(() -> {
            Packet<Object> objectPacket = makePacket();
            objectPacket.setType(PacketType.CLIENT_REGISTER);
            objectPacket.sendTo(p2pServer);
            mainFormController.onServerConnect(this);
        });
    }

    public void openMonitor() throws IOException {
        if(bindLocalAddr==null) return;
        monitorSocket = ServerSocketChannel.open();
        monitorSocket.socket().setReuseAddress(true);
        monitorSocket.bind(p2pServer.getLocalAddress());
        monitorSocket.configureBlocking(false);
        monitorSocket.register(selector, SelectionKey.OP_ACCEPT);
    }


    /**
     * 连接初始化
     */
    public void init() {
        log.info("客户端初始化");
        Stage stage = P2PClientApp.getStage();
        stage.setTitle(String.format("%s@%s",username,uuid));
        try {
            selector = Selector.open();
            connectP2PServer();
            openMonitor();
        } catch (IOException e) {
            log.info("客户端初始化失败");
            e.printStackTrace();
            throw new RuntimeException(e);
        }

    }

    /**
     * 启动
     */
    public void start() {
        if(status>0) return;
        init();
        status = 1;
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                while (status > 0) {
                    try {
                        selector.select();
                        Set<SelectionKey> keys = selector.selectedKeys();
                        Iterator<SelectionKey> iterator = keys.iterator();
                        while (iterator.hasNext()){
                            SelectionKey selectionKey=iterator.next();
                            if(selectionKey.isAcceptable()){
                                log.info("连接事件");
                                executorService.execute(doAccept(selectionKey));
                            }
                            if(selectionKey.isReadable()){
                                log.info("读取事件");
                                if (selectionKey.channel() == p2pServer) {
                                    executorService.execute(fromServerRead(selectionKey));
                                } else {
                                    executorService.execute(fromP2PClient(selectionKey));
                                }
                            }
                            if(selectionKey.isWritable()){

                            }
                            keys.remove(selectionKey);
                        }

                    } catch (IOException exception) {
                        exception.printStackTrace();
                        break;
                    }catch (RuntimeException runtimeException){
                        runtimeException.printStackTrace();
                    }
                }
                close();
            }
        } );

    }

    /**
     * 接收监听来自p2p客户端的连接
     * @param selectionKey
     * @return
     */
    Runnable doAccept(SelectionKey selectionKey) {
        selectionKey.interestOps(0);
        return new Runnable() {
            @Override
            public void run() {
                try {
                    SocketChannel accept = monitorSocket.accept();
                    accept.configureBlocking(false);
                    accept.register(selector, SelectionKey.OP_READ);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    selectionKey.interestOps(SelectionKey.OP_ACCEPT);
                    selector.wakeup();
                }
            }
        };
    }

    /**
     * 来算服务器的消息
     *
     * @param selectionKey
     * @return
     */
    Runnable fromServerRead(SelectionKey selectionKey) {
        selectionKey.interestOps(selectionKey.interestOps()&~SelectionKey.OP_READ);
        return new Runnable() {
            @Override
            public void run() {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                int len=0;
                try {

                    while ((len = p2pServer.read(buffer))>0) {
                        buffer.flip();
                        outputStream.write(buffer.array(), 0, len);
                    }
                    if(len==-1 && outputStream.size()==0){
                        selectionKey.channel();
                        close();
                        return;
                    }
                    String msg = outputStream.toString("utf-8");
                    outputStream.close();
                    log.info("读取来自服务端的消息: {}",msg);
                    Packet packet = JSON.parseObject(msg, Packet.class);
                    switch (packet.getType()) {
                        case SERVER_LIST:
                            JSONArray jsonArray = (JSONArray) packet.getData();
                            List<P2PClientInfo> p2PClientInfos = jsonArray.toJavaList(P2PClientInfo.class);
                            mainFormController.onUpdateList(p2PClientInfos);
                            break;
                        case SERVER_ONLINE:

                        case SERVER_OFFLINE:

                        default:
                            JSONObject jsonObject = (JSONObject) packet.getData();
                            mainFormController.onServerMessage(packet);
                    }
                    selectionKey.interestOps(selectionKey.interestOps()|SelectionKey.OP_READ);
                    selector.wakeup();
                    log.info("读取完成");
                } catch (IOException e) {
                    e.printStackTrace();
                    close();
                } catch (Exception e){
                    e.printStackTrace();
                }finally {
                    try {
                        outputStream.close();
                    } catch (IOException exception) {
                        exception.printStackTrace();
                    }
                }
            }
        };
    }




    Runnable fromP2PClient(SelectionKey selectionKey) {
        selectionKey.interestOps(0);
        return new Runnable() {
            @Override
            public void run() {
                try {

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    selectionKey.interestOps(SelectionKey.OP_READ);
                }
            }
        };
    }

    @PreDestroy
    public void close() {
        log.info("与服务端连接已经关闭");
        try {
            this.status = -1;
            executorService.shutdownNow();
            if (p2pServer != null) p2pServer.close();
            if (monitorSocket != null) monitorSocket.close();
            if (selector != null) {
                selector.close();
                selector.wakeup();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    public SocketChannel getP2pServer() {
        return p2pServer;
    }

    public void setP2pServer(SocketChannel p2pServer) {
        this.p2pServer = p2pServer;
    }


    Packet makePacket(){
        Packet<Object> objectPacket = new Packet<>();
        objectPacket.setUsername(this.username);
        objectPacket.setUuid(this.uuid);
        return objectPacket;
    }


}
